﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using DimensionsHelper.Common.Contracts.Metadata;

// ReSharper disable RedundantSuppressNullableWarningExpression

namespace DimensionsHelper.Services.Metadata;

public abstract class MetadataType(IMetadataDocument document, ObjectType type, string name)
    : MetadataLabeledObject(document, type, name), IMetadataType
{
    public bool Namespace { get; internal set; }

    public IElements Elements { get; } = new Elements(document);
}

[DebuggerDisplay("Name = {Name}")]
public class ElementList : MetadataType, IElementList
{
    internal ElementList(IMetadataDocument document, string name)
        : base(document, ObjectType.ElementList, name)
    {
        Value = 0;
        ReferenceId = string.Empty;
        LevelDepth = 0;
    }


    public int LevelDepth { get; internal set; }

    public string ReferenceId { get; set; }

    public IElementList? Reference
    {
        get
        {
            if (string.IsNullOrEmpty(ReferenceId) ||
                !Document.Types.TryGetByKey(ReferenceId, out var list))
            {
                return null;
            }

            return list;
        }
    }

    public int Value { get; }

    public IElements GetElements()
    {
        return Elements.Reference?.Elements ?? Elements;
    }
}

public abstract class MetadataTypes<T>(
    IMetadataDocument document,
    ObjectType objectType,
    IMetadataObject? parent = null)
    : MetadataLabeledCollection<T>(document, objectType, parent), IMetadataTypes<T>
    where T : class, IMetadataType
{
    public bool Namespace { get; set; }


    protected override string KeySelector(T item)
    {
        return item.Id;
    }
}

public class MetadataTypes : MetadataTypes<IElementList>, IMetadataTypes
{
    internal MetadataTypes(IMetadataDocument document, IMetadataObject parent)
        : base(document, ObjectType.Types, parent)
    {
    }

    internal MetadataTypes(IMetadataDocument document)
        : this(document, document)
    {
    }

    public override IElementList CreateObject(string name)
    {
        return new ElementList(Document, name);
    }

    protected override string KeySelector(IElementList item)
    {
        return item.Id;
    }
}

public partial class MetadataValueRanges : IMetadataValueRanges
{
    private readonly List<MetadataValueRange> _ranges;

    internal MetadataValueRanges(IField owner)
    {
        _ranges = [];
        ValueType = MetadataRangeValueType.Integer;
        OwnerField = owner;
    }

    [GeneratedRegex(@"(?<=\[|,)\s*([^.\s]*?)\s*\.\.\s*([^.\s]*?)\s*(?i:\s+step\s+([^,\]\s]+))?(?=\]|,)",
        RegexOptions.None)]
    private static partial Regex RangeRegex();

    public MetadataValueRange this[int index] => _ranges[index];

    public IField OwnerField { get; }

    public int Count => _ranges.Count;

    public object? LowerBound => _ranges.Count == 0 ? null : _ranges[0].LowerBound;

    public object? UpperBound => _ranges.Count == 0 ? null : _ranges[_ranges.Count - 1].UpperBound;

    public MetadataRangeValueType ValueType { get; private set; }

    public string RangeExpression
    {
        get
        {
            var expr = new StringBuilder();
            expr.Append('[');

            foreach (var item in _ranges)
            {
                if (expr.Length > 1)
                {
                    expr.Append(", ");
                }

                expr.Append(item.GetExpression());
            }

            expr.Append(']');
            return expr.ToString();
        }
    }


    public bool TryFind(object lowerBound, object upperBound,
        [NotNullWhen(true)] out MetadataValueRange range,
        out MetadataRangeOverlapType overlapType)
    {
        range = null!;
        overlapType = MetadataRangeOverlapType.Equal;

        foreach (var r in _ranges)
        {
            var comp = r.CheckOverlap(lowerBound, upperBound);

            switch (comp)
            {
                case MetadataRangeOverlapType.Equal:
                case MetadataRangeOverlapType.Cover:
                case MetadataRangeOverlapType.Include:
                case MetadataRangeOverlapType.LeftIntersection:
                case MetadataRangeOverlapType.RightIntersection:
                case MetadataRangeOverlapType.LeftNeighbourhood:
                case MetadataRangeOverlapType.RightNeighbourhood:
                    range = r;
                    overlapType = comp;
                    return true;

                case MetadataRangeOverlapType.LeftNoIntersection:
                case MetadataRangeOverlapType.RightNoIntersection:
                default:
                    break;
            }
        }

        return false;
    }


    public int IndexOf(object value)
    {
        value = ChangeType(value);

        for (var i = 0; i < _ranges.Count; i++)
        {
            var r = _ranges[i];

            if (r.CompareValue(value) <= 0)
            {
                return i;
            }
        }

        return -1;
    }


    public MetadataValueRange? Find(object value)
    {
        value = ChangeType(value);
        var index = IndexOf(value);
        return index >= 0 ? _ranges[index] : null;
    }


    public void Add(object? upperBound, object? lowerBound, object? step = null)
    {
        // 如果step不是null，当upperBound和lowerBound和已有区间重合时，
        // 需要检查step是否相同，如果不同，需要进行子区间的拆分。

        if (upperBound == null && lowerBound == null)
        {
            _ranges.Clear();
            _ranges.Add(new MetadataValueRange(OwnerField, null, null, step));
            ValueType = MetadataRangeValueType.Integer;
            return;
        }

        // 如果当前区间列表是空，直接添加第一个子区间，并将添加的数据类型固定成列表的数据类型。
        if (_ranges.Count == 0)
        {
            switch (upperBound ?? lowerBound)
            {
                case string _:
                    ValueType = MetadataRangeValueType.String;
                    break;
                case int _:
                    ValueType = MetadataRangeValueType.Integer;
                    break;
                case long _:
                    ValueType = MetadataRangeValueType.Long;
                    break;
                case bool _:
                    ValueType = MetadataRangeValueType.Boolean;
                    break;
                case DateTime _:
                    ValueType = MetadataRangeValueType.Date;
                    break;
                default:
                    ValueType = MetadataRangeValueType.Integer;
                    break;
            }

            if (lowerBound != null)
            {
                lowerBound = ChangeType(lowerBound);
            }

            if (upperBound != null)
            {
                upperBound = ChangeType(upperBound);
            }

            _ranges.Add(new MetadataValueRange(OwnerField, lowerBound, upperBound, step));

            return;
        }

        int findIndex;

        // 此时，lowerBound 和 upperBound 不可能同时为 null。
        // 提前检查并尝试转换上下限的数据类型。
        if (lowerBound != null)
        {
            lowerBound = ChangeType(lowerBound);
        }

        if (upperBound != null)
        {
            upperBound = ChangeType(upperBound);
        }

        // 插入区间是 [x .. ] 时，只检查下限，只保留区间上限小于x的子区间
        // 下限大于等于x的子区间合并成 [x .. ]
        if (upperBound == null && lowerBound != null)
        {
            findIndex = IndexOf(lowerBound);

            // 如果x大于所有子区间的上限，将 [x .. ] 插入到子区间列表最后
            if (findIndex == -1)
            {
                _ranges.Add(new MetadataValueRange(OwnerField, lowerBound, null, step));
            }
            else
            {
                var updateItem = _ranges[findIndex];

                // [[a, b] .., ][[c] .. d][, [e .. f] ...]
                //                     ^
                //                    [x ..]
                // x 在区间内时，移除下限大于x的所有区间，并将包含x的区间上限更新成null
                if (updateItem.CompareValue(lowerBound) == 0)
                {
                    if (findIndex < _ranges.Count - 1)
                    {
                        _ranges.RemoveRange(findIndex + 1, _ranges.Count - findIndex - 1);
                    }

                    updateItem.Expand(MetadataRangeBoundaryType.Upper, null);
                    updateItem.Step = step;
                }
                // [[a, b] .., ][[c] .. d][, [e .. f] ...]
                //                        ^
                //                       [x ..]
                // x 不在区间内时，移除包括 findIndex 在内的后续子区间
                else
                {
                    _ranges.RemoveRange(findIndex, _ranges.Count - findIndex);

                    if (findIndex > 0)
                    {
                        _ranges[findIndex - 1].Expand(MetadataRangeBoundaryType.Upper, null);
                        _ranges[findIndex - 1].Step = step;
                    }
                    else
                    {
                        _ranges.Add(new MetadataValueRange(OwnerField, lowerBound, null, step));
                    }
                }
            }

            return;
        }

        // 插入区间是 [ .. x] 时， 只检查下限，只保留下限大于x的子区间
        // 上限小于等于x的子区间合并成 [ .. x]
        if (lowerBound == null && upperBound != null)
        {
            findIndex = IndexOf(upperBound);

            // 如果x大于当前所有子区间的上限，将当前区间列表更新成 [..x]
            if (findIndex == -1)
            {
                _ranges.Clear();
                _ranges.Add(new MetadataValueRange(OwnerField, null, upperBound, step));
            }
            else
            {
                var updateItem = _ranges[findIndex];

                if (findIndex > 0)
                {
                    _ranges.RemoveRange(0, findIndex);
                }

                // [[a, b] .., ][[c] .. d][, [e .. f] ...]
                //                   ^
                //              [ .. x]
                // x在区间内时，更新区间
                if (updateItem.CompareValue(upperBound) == 0)
                {
                    // [[c] .. d] -> [ .. d]
                    updateItem.Expand(MetadataRangeBoundaryType.Lower, null);
                    updateItem.Step = step;
                }
                // [[a, b] .., ][[c] .. d][, [e .. f] ...]
                //                        ^
                //                   [ .. x]
                // x 不在区间内时，检查插入位置，移除所有上限小于x的子区间
                else
                {
                    _ranges.Insert(0, new MetadataValueRange(OwnerField, null, upperBound, step));
                }
            }

            return;
        }


        //  假设当前列表子区间：
        //     [a, b], [c, d], [e, f]
        //  假设插入区间：
        //     [g, h]
        //  考虑如下几种情况：
        //     1. g > f -> [a, b], [c, d], [e, f], [g, h]
        //     2. g = f -> [a, b], [c, d], [e, h]
        //     3. h < a -> [g, h], [a, b], [c, d], [e, f]
        //     4. h = a -> [g, b], [c, d], [e, f]
        //     5. 插入到中间，需要检查相邻的子区间：
        //        <1>. 无交集例如：
        //        [a, b], [c, d], [e, f]
        //               ^
        //             [g, h]
        //         <2>. 有交集例如：
        //         [a, b], [c, d], [e, f]
        //             [g, ..       h]
        //         此时，可能横跨多个子集，涉及到的子集都应当合并到一个并集中。

        if (lowerBound == null)
        {
            return;
        }

        findIndex = IndexOf(lowerBound);

        // lowerBound 大于所有区间时，直接将新子区间添加到列表末尾
        if (findIndex == -1)
        {
            _ranges.Add(new MetadataValueRange(OwnerField, lowerBound, upperBound, step));
            return;
        }

        // 第一个区间大于 lowerBound 时，新插入一个 [lowerBound .. ] 到列表最前，
        MetadataValueRange updateRange;

        if (findIndex == 0)
        {
            updateRange = new MetadataValueRange(OwnerField, lowerBound, null, step);
        }
        else
        {
            updateRange = _ranges[findIndex - 1];
            findIndex--;
        }

        if (upperBound == null)
        {
            return;
        }

        // 向后查找符合 upperBound 的子区间。
        var count = 0;

        for (var i = findIndex + 1; i < _ranges.Count; i++)
        {
            var compare = _ranges[i].CompareValue(upperBound);

            // upperBound 在区间内时，将此区间的上限更新到updateRange的上限，最后需要移除此子区间。
            if (compare == 0)
            {
                updateRange.Expand(MetadataRangeBoundaryType.Upper, _ranges[i].UpperBound);
                count++;
                break;
            }
            // updateBound 小于子区间时，将updateRange的上限更新成upperBound，最后不移除此子区间。

            if (compare < 0)
            {
                updateRange.Expand(MetadataRangeBoundaryType.Upper, upperBound);
                break;
            }

            count++;
        }

        if (count > 0)
        {
            _ranges.RemoveRange(findIndex + 1, count);
        }
    }


    public void RemoveAt(int index)
    {
        _ranges.RemoveAt(index);
    }


    public void AddExpression(string expression)
    {
        var matches = RangeRegex().Matches(expression);

        foreach (var item in matches.Cast<Match>())
        {
            var lower = GetRangeValue(item.Groups[1].Value);
            var upper = GetRangeValue(item.Groups[2].Value);
            var step = GetRangeValue(item.Groups[3].Value);

            if (lower != null || upper != null)
            {
                Add(upper, lower, step);
            }
        }
    }


    private object ChangeType(object value)
    {
        switch (ValueType)
        {
            case MetadataRangeValueType.Long:
                switch (value)
                {
                    case long l:
                        return l;
                    case string ls when long.TryParse(ls, out var lv):
                        return lv;
                    default:
                        return 0;
                }

            case MetadataRangeValueType.Integer:
                switch (value)
                {
                    case int i:
                        return i;
                    case string intStr when int.TryParse(intStr, out var intValue):
                        return intValue;
                    default:
                        return 0;
                }

            case MetadataRangeValueType.Date:
                switch (value)
                {
                    case DateTime dt:
                        return dt;
                    case string dtStr when DateTime.TryParse(dtStr, out var dtValue):
                        return dtValue;
                    default:
                        throw new Exception($"无法将类型是'{value.GetType().Name}'的值'{value}'转换成'{nameof(DateTime)}'类型。");
                }

            case MetadataRangeValueType.String:
                if (value is string str)
                {
                    return str;
                }

                return value.ToString() ?? "";

            case MetadataRangeValueType.Boolean:
                switch (value)
                {
                    case bool bln:
                        return bln;
                    case string blnStr when bool.TryParse(blnStr, out var blnValue):
                        return blnValue;
                    default:
                        return false;
                }

            default:
                return value;
        }
    }


    private static object? GetRangeValue(string value)
    {
        if (string.IsNullOrEmpty(value))
        {
            return null;
        }

        if (value.StartsWith("\"") && value.EndsWith("\""))
        {
            var strValue = value.Substring(1, value.Length - 2);
            if (DateTime.TryParse(strValue, out var dt))
            {
                return dt;
            }

            return strValue;
        }

        if (int.TryParse(value, out var iValue))
        {
            return iValue;
        }

        if (double.TryParse(value, out var dValue))
        {
            return dValue;
        }

        return value;
    }


    public static IMetadataValueRanges FromExpression(IField ownerField, string expression)
    {
        var ranges = new MetadataValueRanges(ownerField);
        ranges.AddExpression(expression);
        return ranges;
    }


    #region IEnumerable

    public IEnumerator<MetadataValueRange> GetEnumerator()
    {
        return _ranges.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    #endregion
}

[DebuggerDisplay("FullName = {FullName}")]
public class Field : MetadataType, IField
{
    internal Field(IMetadataDocument document, string name, FieldDataType type)
        : base(document, ObjectType.Field, name)
    {
        Id = Guid.NewGuid().ToString();
        DataType = type;
        Ranges = new MetadataValueRanges(this);
        FieldInfo = FieldInfoTypes.None;
        Categories = new Categories(document, this);
        ((Elements)Elements).Owner = this;
        IsSystem = false;
        ReferenceId = null;
        LevelDepth = 0;
    }

    public int LevelDepth { get; internal set; }

    public FieldInfoTypes FieldInfo { get; internal set; }

    public FieldDataType DataType { get; internal set; }

    public string? ReferenceId { get; protected set; }

    public virtual IField? Reference
    {
        get
        {
            if (!string.IsNullOrEmpty(ReferenceId) &&
                Document.Variables.TryGetByKey(ReferenceId!, out var variable))
            {
                return variable;
            }

            return null;
        }
    }

    public IMetadataValueRanges Ranges { get; }

    public ICategories Categories { get; }

    public bool IsSystem { get; internal set; }
}

public class Fields : MetadataTypes<IField>, IFields
{
    internal Fields(IMetadataDocument document, IMetadataObject parent)
        : base(document, ObjectType.Fields, parent)
    {
        Name = "@fields";
        Versioned = true;
    }

    internal Fields(IMetadataDocument document)
        : this(document, document)
    {
    }


    public override IField CreateObject(string name)
    {
        return new Field(Document, name, FieldDataType.Categorical);
    }

    public IArray CreateArray(string name, string id = "")
    {
        var arr = new MetadataArray(Document, name);

        if (!string.IsNullOrEmpty(id))
        {
            arr.Id = id;
        }

        return arr;
    }

    public IVariable CreateVariableReference(string name, string referenceId, string? fullName = null,
        string id = "")
    {
        var varInstance = new Variable(Document, name, referenceId);

        if (!string.IsNullOrEmpty(id))
        {
            varInstance.Id = id;
        }

        if (!string.IsNullOrEmpty(fullName))
        {
            varInstance.FullName = fullName!;
        }

        return varInstance;
    }

    public ICompound CreateCompound(string name, string id = "")
    {
        var compound = new Compound(Document, name);

        if (!string.IsNullOrEmpty(id))
        {
            compound.Id = id;
        }

        return compound;
    }

    public IBlock CreateBlock(string name, string id = "")
    {
        var block = new Block(Document, name);

        if (!string.IsNullOrEmpty(id))
        {
            block.Id = id;
        }

        return block;
    }

    protected override void OnItemAdded(IField item)
    {
        base.OnItemAdded(item);
        ((Field)item).Parent = this;
    }
}

public class HelperFields : MetadataStringKeyCollection<IVariable>, IHelperFields
{
    internal HelperFields(IMetadataDocument document, IMetadataObject parent)
    {
        Document = document;
        Parent = parent;
        ObjectType = ObjectType.HelperFields;
        Properties = new Properties(document, this);
        Name = "@helperfields";
        Versioned = true;
        Namespace = false;
    }


    public IMetadataDocument Document { get; }

    public ObjectType ObjectType { get; }

    public string Name { get; }

    public string FullName => MetadataNamedObject.GetFullName(this);

    public IMetadataObject Parent { get; }

    public IVersionObjectCollection? Versions { get; internal set; }

    public T? FindParent<T>() where T : IMetadataObject
    {
        return MetadataObject.FindParent<T>(this);
    }

    public IProperties Properties { get; }

    public bool Namespace { get; set; }

    public bool Versioned { get; set; }

    public override IVariable CreateObject(string name)
    {
        return new Variable(Document, name, "");
    }

    protected override string KeySelector(IVariable item)
    {
        return item.FullName;
    }
}

public class Variable : Field, IVariable
{
    internal Variable(IMetadataDocument document, string name, string reference)
        : base(document, name, FieldDataType.None)
    {
        Init();
        DataSourceProperties = new Properties(Document, this);
        HelperFields = new HelperFields(Document, this);
        ReferenceId = reference;
    }


    internal Variable(IMetadataDocument document, string name, FieldDataType dataType)
        : base(document, name, dataType)
    {
        Init();
        DataSourceProperties = new Properties(Document, this);
        HelperFields = new HelperFields(Document, this);
    }

    public IProperties DataSourceProperties { get; private set; }

    public bool NoCaseData { get; internal set; }

    public VariableUsage UsageType { get; internal set; }

    public IHelperFields HelperFields { get; private set; }

    public IVariable? Codes =>
        HelperFields.FirstOrDefault(item => item.Name.Equals("Codes", StringComparison.OrdinalIgnoreCase));

    public ICategories? OtherCategories { get; internal set; }

    public object? MinValue { get; internal set; }

    public object? MaxValue { get; internal set; }

    public object? EffectiveMinValue { get; internal set; }

    public object? EffectiveMaxValue { get; internal set; }

    public object? InitialAnswer { get; internal set; }

    public object? DefaultAnswer { get; internal set; }

    public int? Precision { get; internal set; }

    public string? Expression { get; internal set; }

    public string? AxisExpression { get; internal set; }

    public string? Validation { get; set; }

    public bool IsDbQuestion { get; internal set; }

    public DbQuestionTypes DbQuestionType { get; internal set; }

    public FieldDataType DbDataType { get; internal set; }

    public IDbElements? DbElements { get; private set; }

    public Orientations Orientation { get; set; }

    public int? Scale { get; set; }

    public bool AutoElements { get; internal set; }

    private void Init()
    {
        ReferenceId = null;
        NoCaseData = false;
        UsageType = VariableUsage.Variable;
        OtherCategories = null;
        MinValue = null;
        MaxValue = null;
        EffectiveMinValue = null;
        EffectiveMaxValue = null;
        Versioned = true;
        InitialAnswer = null;
        DefaultAnswer = null;
        Precision = null;
        Expression = null;
        IsSystem = false;
        IsDbQuestion = false;
        AutoElements = false;
        DbQuestionType = DbQuestionTypes.Unknown;
        DbDataType = FieldDataType.None;
        DbElements = new DbElements(Document, this);
        Orientation = Orientations.Default;
        Validation = null;
        Scale = null;
    }
}

public class PageItem : MetadataNamedObject, IPageItem
{
    internal PageItem(IMetadataDocument document, string name, IMetadataObject parent)
        : base(document, ObjectType.Unknown, name)
    {
        Id = Guid.NewGuid().ToString();
        Parent = parent;
        ReferenceId = string.Empty;
    }

    public string Id { get; internal set; }

    public IVariable? Reference =>
        Document.Variables.TryGetByKey(ReferenceId, out var obj) ? obj : null;

    public string ReferenceId { get; set; }
}

public class Page : MetadataLabeledCollection<IPageItem>, IPage
{
    internal Page(IMetadataDocument document, string name, IMetadataObject parent)
        : base(document, ObjectType.Page, parent)
    {
        Name = name;
        Namespace = false;
        ReferenceId = string.Empty;
    }

    public bool Namespace { get; internal set; }

    public string ReferenceId { get; set; }

    public IPage? Reference =>
        Document.Pages.TryGetByKey(ReferenceId, out var p) ? p : null;

    public override IPageItem CreateObject(string name)
    {
        return new PageItem(Document, name, this);
    }


    protected override string KeySelector(IPageItem item)
    {
        return item.Id;
    }
}

public class Pages : MetadataLabeledCollection<IPage>, IPages
{
    internal Pages(IMetadataDocument document, IMetadataObject parent)
        : base(document, ObjectType.Pages, parent)
    {
        Name = "@pages";
        Namespace = false;
    }

    public bool Namespace { get; internal set; }

    public override IPage CreateObject(string name)
    {
        return new Page(Document, name, this);
    }


    protected override string KeySelector(IPage item)
    {
        return item.Id;
    }
}

public class MetadataClass : Field, IMetadataClass
{
    internal MetadataClass(IMetadataDocument document, IMetadataObject parent)
        : base(document, "@class", FieldDataType.None)
    {
        ObjectType = ObjectType.Class;
        Parent = parent;
        Types = new MetadataTypes(document, this);
        Fields = new Fields(document, this);
        Pages = new Pages(document, this);
        Routings = new Routings(document);
        Namespace = false;
        DataSourceProperties = new Properties(document, this);
    }

    public IMetadataTypes Types { get; }

    public IFields Fields { get; }

    public IPages Pages { get; }

    public IRoutings Routings { get; }

    public IProperties DataSourceProperties { get; }
}

public class MetadataArray : Field, IArray
{
    internal MetadataArray(IMetadataDocument document, string name)
        : base(document, name, FieldDataType.None)
    {
        Id = "_" + Id;
        ObjectType = ObjectType.Array;
        FieldInfo = FieldInfoTypes.None;
        IteratorType = IteratorTypes.Categorical;
        ArrayType = ArrayTypes.Expand;
        Orientation = Orientations.Default;
        Class = new MetadataClass(document, this);
        IsGrid = false;
        IsDbQuestion = false;
        DbQuestionType = DbQuestionTypes.Unknown;
        DbDataType = FieldDataType.None;
        DbElements = new DbElements(document, this);
        DeriveElements = false;
        AxisExpression = null;
        DefaultAnswer = null;
        InitialAnswer = null;
        DataType = FieldDataType.Categorical;
        Namespace = false;
        HelperFields = new HelperFields(document, this);
        Versioned = true;
        AutoElements = false;
    }

    public IteratorTypes IteratorType { get; internal set; }

    public ArrayTypes ArrayType { get; internal set; }

    public Orientations Orientation { get; internal set; }

    public IMetadataClass Class { get; }

    public bool IsDbQuestion { get; internal set; }

    public DbQuestionTypes DbQuestionType { get; internal set; }

    public FieldDataType DbDataType { get; internal set; }

    public IDbElements DbElements { get; }

    public bool IsGrid { get; internal set; }

    public bool DeriveElements { get; internal set; }

    public string? AxisExpression { get; internal set; }

    public object? DefaultAnswer { get; internal set; }

    public object? InitialAnswer { get; internal set; }

    public IHelperFields HelperFields { get; }

    public bool AutoElements { get; set; }


    public IElementList CreateElementList(string name)
    {
        var list = new ElementList(Document, name);
        ((MetadataTypes)Document.Types).Add(list);

        // 创建引用
        var elementsRef = new ElementList(Document, name)
        {
            LevelDepth = LevelDepth + 1,
            ReferenceId = list.Id
        };
        ((MetadataTypes)Class.Types).Add(elementsRef);

        return list;
    }

    public IVariable CreateVariable(string name, FieldDataType dataType)
    {
        var variable = Document.CreateVariable(name, dataType);
        var reference = (Variable)Class.Fields.CreateVariableReference(name, variable.Id);
        reference.LevelDepth = LevelDepth + 1;
        ((Fields)Class.Fields).Add(reference);
        return variable;
    }

    public IArray CreateArray(string name, IteratorTypes iteratorType)
    {
        var arr = new MetadataArray(Document, name)
        {
            IteratorType = iteratorType,
            LevelDepth = LevelDepth + 1
        };

        ((Fields)Class.Fields).Add(arr);

        return arr;
    }


    public IBlock CreateBlock(string name)
    {
        var block = new Block(Document, name)
        {
            LevelDepth = LevelDepth + 1
        };
        ((Fields)Class.Fields).Add(block);

        return block;
    }
}

public class Block : Field, IBlock
{
    internal Block(IMetadataDocument document, string name)
        : base(document, name, FieldDataType.None)
    {
        Id = "_" + Id;
        ObjectType = ObjectType.Class;
        FieldInfo = FieldInfoTypes.None;
        Types = new MetadataTypes(document, this);
        Fields = new Fields(document, this);
        Routings = new Routings(document);
        Pages = new Pages(document, this);
        Namespace = false;
    }

    public IMetadataTypes Types { get; }

    public IFields Fields { get; }

    public IRoutings Routings { get; }

    public IPages Pages { get; }


    public IElementList CreateElementList(string name)
    {
        var list = new ElementList(Document, name);
        ((MetadataTypes)Document.Types).Add(list);

        // 创建引用
        var elementsRef = new ElementList(Document, name)
        {
            LevelDepth = LevelDepth + 1,
            ReferenceId = list.Id
        };
        ((MetadataTypes)Types).Add(elementsRef);

        return list;
    }


    public IVariable CreateVariable(string name, FieldDataType dataType)
    {
        var variable = Document.CreateVariable(name, dataType);
        var reference = new Variable(Document, variable.Name, variable.Id)
        {
            Id = "_" + variable.Id,
            LevelDepth = LevelDepth + 1
        };
        ((Fields)Fields).Add(reference);
        return variable;
    }


    public IArray CreateArray(string name, IteratorTypes iteratorType)
    {
        var arr = new MetadataArray(Document, name)
        {
            IteratorType = iteratorType,
            LevelDepth = LevelDepth + 1
        };

        ((Fields)Fields).Add(arr);

        return arr;
    }

    public IBlock CreateBlock(string name)
    {
        var block = new Block(Document, name)
        {
            LevelDepth = LevelDepth + 1
        };
        ((Fields)Fields).Add(block);
        return block;
    }


    public IPage CreatePage(string name)
    {
        var page = Pages.CreateObject(name);
        ((Pages)Pages).Add(page);
        return page;
    }
}

public class Variables : MetadataTypes<IVariable>, IVariables
{
    internal Variables(IMetadataDocument document, IMetadataObject parent) :
        base(document, ObjectType.Variables, parent)
    {
    }


    public override IVariable CreateObject(string name)
    {
        return new Variable(Document, name, FieldDataType.None);
    }

    public IVariable CreateReference(string name, string referenceId, string id = "")
    {
        var variable = new Variable(Document, name, referenceId);

        if (!string.IsNullOrEmpty(id))
        {
            variable.Id = id;
        }

        return variable;
    }
}

public class Compound : Field, ICompound
{
    internal Compound(IMetadataDocument document, string name, string id = "")
        : base(document, name, FieldDataType.Categorical)
    {
        if (string.IsNullOrEmpty(id))
        {
            id = Guid.NewGuid().ToString();
        }

        Id = id;
        ObjectType = ObjectType.Compound;
        Types = new MetadataTypes(document, this);
        Fields = new Fields(document, this);
        Pages = new Pages(document, this);
        Routings = new Routings(document);
        Orientation = Orientations.Default;

        ((Elements)Elements).ItemChanged += OnElementsItemChanged;
    }

    public IMetadataTypes Types { get; }

    public IFields Fields { get; }

    public IPages Pages { get; }

    public IRoutings Routings { get; }

    public Orientations Orientation { get; set; }


    public IVariable CreateVariable(string name, FieldDataType dataType)
    {
        var variable = Document.CreateVariable(name, dataType);
        ((Variable)variable).AutoElements = true;

        if (dataType == FieldDataType.Categorical)
        {
            foreach (var element in Elements)
            {
                ((Elements)variable.Elements).Add(element);
            }
        }

        var reference = new Variable(Document, name, variable.Id)
        {
            Id = "_" + variable.Id
        };
        ((Fields)Fields).Add(reference);
        return variable;
    }

    public IArray CreateArray(string name)
    {
        var arr = new MetadataArray(Document, name)
        {
            IteratorType = IteratorTypes.Categorical, // 固定是 Categorical
            AutoElements = true
        };

        ((Elements)arr.Elements).IsDerived = true;

        foreach (var element in Elements)
        {
            ((Elements)arr.Elements).Add(element);
        }

        ((Fields)Fields).Add(arr);

        return arr;
    }

    private void OnElementsItemChanged(object? sender, MetadataObjectCollectionChangedEventArgs<IElement> args)
    {
        switch (args.ChangeType)
        {
            case MetadataObjectCollectionItemChangeType.Add:
                foreach (var field in Fields)
                {
                    if ((field is IVariable && field.DataType == FieldDataType.Categorical) || field is IArray)
                    {
                        ((Elements)field.Elements).Add(args.NewItem!);
                    }
                }

                break;

            case MetadataObjectCollectionItemChangeType.Remove:
                foreach (var field in Fields)
                {
                    if ((field is IVariable && field.DataType == FieldDataType.Categorical) || field is IArray)
                    {
                        ((Elements)field.Elements).Remove(args.OldItem!);
                    }
                }

                break;
        }
    }
}

public class VariableInstance : MetadataNamedObject, IVariableInstance
{
    internal VariableInstance(IMetadataDocument document, string name)
        : base(document, ObjectType.VariableInstance, name)
    {
        SourceType = SourceTypes.None;
        VariableId = string.Empty;
        Elements = new ElementInstances(document, this);
    }

    public SourceTypes SourceType { get; set; }

    public string VariableId { get; set; }


    public IVariable? Variable
    {
        get
        {
            if (string.IsNullOrEmpty(VariableId) || !Document.Variables.TryGetByKey(VariableId, out var obj))
            {
                return null;
            }

            return obj;
        }
    }

    public IElementInstances Elements { get; }
}

public class VariableInstances : MetadataObjectCollection<IVariableInstance>, IVariableInstances
{
    internal VariableInstances(IMetadataDocument document)
    {
        Document = document;
    }

    public IMetadataDocument Document { get; }


    public IVariableInstance CreateObject(string name, string referenceId, string? fullName = null)
    {
        var instance = new VariableInstance(Document, name)
        {
            VariableId = referenceId
        };

        if (!string.IsNullOrEmpty(fullName))
        {
            instance.FullName = fullName!;
        }

        return instance;
    }
}